#include "HttpResponse.h"

class CHttpResponseToken
{
public:
    enum class EHttpResponseTokenType
    {
        PlainString,
        Colon, // ":"
        Space, // " "  ,only parse first two space
        CRLF, // "\r\n" 
    };

    EHttpResponseTokenType Type;
    CString PlainString; // only for PlainString type
};

void ParseResponseString(TVector<CHttpResponseToken>& OutTokens
    , const CString& InResponseString)
{
    OutTokens.Clear();

    CHttpResponseToken Token;
    bool IsHeadFinished = false;
    uint8_t SpaceCount = 0; 
    bool ParseingHeadValue = false; //if true, next token must be PlainString until CRLF
    for (int i = 0; i < InResponseString.GetLength(); i++)
    {

        if (!IsHeadFinished && !ParseingHeadValue && InResponseString[i] == U':')
        {
            if(Token.PlainString.GetLength()>0)
            {
                OutTokens.Add(Token);
                Token = CHttpResponseToken();
            }

            Token.Type = CHttpResponseToken::EHttpResponseTokenType::Colon;
            OutTokens.Add(Token);
            ParseingHeadValue=true; 
            Token = CHttpResponseToken();
        }
        else if (SpaceCount<2 &&  InResponseString[i] == U' ')
        {
            if(Token.PlainString.GetLength()>0)
            {
                OutTokens.Add(Token);
                Token = CHttpResponseToken();
            }

            Token.Type = CHttpResponseToken::EHttpResponseTokenType::Space;
            OutTokens.Add(Token);
            SpaceCount++;

            Token = CHttpResponseToken();

        }
        else if (!IsHeadFinished && InResponseString[i] == U'\r' && InResponseString[i + 1] == U'\n')
        {
            if(Token.PlainString.GetLength()>0)
            {
                OutTokens.Add(Token);
                Token = CHttpResponseToken();
            }

            //if former token is CRLF, mark response head finished
            if(OutTokens.Num()>0
                && OutTokens[OutTokens.Num()-1].Type==CHttpResponseToken::EHttpResponseTokenType::CRLF
            )
            {
                IsHeadFinished = true;
            }

            Token.Type = CHttpResponseToken::EHttpResponseTokenType::CRLF;
            OutTokens.Add(Token);
            ParseingHeadValue=false;
            Token = CHttpResponseToken();
            i++;
        }
        else
        {
            Token.Type = CHttpResponseToken::EHttpResponseTokenType::PlainString;
            Token.PlainString += InResponseString[i];
        }
    }
    if(Token.PlainString.GetLength()>0)
    {
        OutTokens.Add(Token);
    }
}


std::shared_ptr<CHttpResponse> CHttpResponse::Parse(const CString &InResponseString)
{
    TVector<CHttpResponseToken> Tokens;
    ParseResponseString(Tokens,InResponseString);

    if(Tokens.Num()<6) //status line tokens number == 6
    {
        return nullptr;
    }

    auto Response = std::make_shared<CHttpResponse>();

    //parse status line
    //parse http version
    if(Tokens[0].Type!=CHttpResponseToken::EHttpResponseTokenType::PlainString)
    {
        return nullptr;
    }
    Response->HttpVersion = Tokens[0].PlainString;

    //parse statues code
    if(Tokens[1].Type != CHttpResponseToken::EHttpResponseTokenType::Space
        || Tokens[2].Type!=CHttpResponseToken::EHttpResponseTokenType::PlainString
        || !Tokens[2].PlainString.IsDigit()
    )
    {
        return nullptr;
    }
    Response->StatuesCode = (EHttpResponseCode)Tokens[2].PlainString.ToUInt64();

    //parse statues string
    if(Tokens[3].Type!=CHttpResponseToken::EHttpResponseTokenType::Space
        || Tokens[4].Type!=CHttpResponseToken::EHttpResponseTokenType::PlainString
    )
    {
        return nullptr;
    }
    Response->StatuesString = Tokens[4].PlainString;

    //skip CRLF after status line
    if(Tokens[5].Type!=CHttpResponseToken::EHttpResponseTokenType::CRLF)
    {
        return nullptr;
    }

    //parse head
    uint32_t TokenIndex = 6;
    {
        CString Key;
        CString Value;
        for(;TokenIndex<Tokens.Num();TokenIndex+=4)
        {
            if((TokenIndex+4)>=Tokens.Num())
            {
                //head finished
                break;
            }

            if(Tokens[TokenIndex].Type!=CHttpResponseToken::EHttpResponseTokenType::PlainString
                || Tokens[TokenIndex+1].Type!=CHttpResponseToken::EHttpResponseTokenType::Colon
                || Tokens[TokenIndex+2].Type!=CHttpResponseToken::EHttpResponseTokenType::PlainString
                || Tokens[TokenIndex+3].Type!=CHttpResponseToken::EHttpResponseTokenType::CRLF
            )
            {
                //invalid head
                break;
            }

            Key=Tokens[TokenIndex].PlainString;
            Value=Tokens[TokenIndex+2].PlainString;
            Response->HeadParameters.Add(Key,Value);
        }
    }

    //skip CRLF
    if(Tokens[TokenIndex].Type!=CHttpResponseToken::EHttpResponseTokenType::CRLF)
    {
        //response head must end with CRLF
        return nullptr;
    }
    else
    {
        TokenIndex++;
    }

    //parse body
    {
        if(TokenIndex>=Tokens.Num())
        {
            //no body
            return Response;
        }

        if(Tokens[TokenIndex].Type!=CHttpResponseToken::EHttpResponseTokenType::PlainString)
        {
            //invalid body
            return nullptr;
        }

        Response->Body = Tokens[TokenIndex].PlainString;
    }

    return Response;
}

CString CHttpResponse::ToString() const
{
    CString ResponseString;

    //status line
    ResponseString += HttpVersion;
    ResponseString += U" ";
    ResponseString += CString((uint64_t)StatuesCode);
    ResponseString += U" ";
    ResponseString += StatuesString;
    ResponseString += U"\r\n";

    //head
    for(auto &HeadParameter:HeadParameters)
    {
        ResponseString += HeadParameter.Key;
        ResponseString += U": ";
        ResponseString += HeadParameter.Value;
        ResponseString += U"\r\n";
    }

    //CRLF
    ResponseString += U"\r\n";

    //body
    ResponseString += Body;

    return ResponseString;
}

std::shared_ptr<CHttpResponse> CHttpResponse::NotFoundResponse()
{
    auto Response = std::make_shared<CHttpResponse>();
    Response->HttpVersion = U"HTTP/1.1";
    Response->StatuesCode = EHttpResponseCode::NotFound;
    Response->StatuesString = U"Not Found";
    Response->HeadParameters.Add(U"Content-Type",U"text/html");
    Response->Body = U"<html><body><h1>404 Not Found\n</h1>generated by guo engine</body></html>";
    return Response;    

}

std::shared_ptr<CHttpResponse> CHttpResponse::OKResponse()
{
    auto Response = std::make_shared<CHttpResponse>();
    Response->HttpVersion = U"HTTP/1.1";
    Response->StatuesCode = EHttpResponseCode::OK;
    Response->StatuesString = U"OK";
    // Response->HeadParameters.Add(U"Content-Type",U"text/html");
    // Response->Body = U"<html><body><h1>200 OK\n</h1>generated by guo engine</body></html>";
    return Response;    

}
